Take advantage of the Span<T> namespace changes#8477
Conversation
6b128fd to
49790cb
Compare
BenchmarksBenchmark execution time: 2026-04-24 12:37:36 Comparing candidate commit d1485d3 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 27 metrics, 0 unstable metrics, 60 known flaky benchmarks, 27 flaky benchmarks without significant changes.
|
49790cb to
25f6d5e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25f6d5e446
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| { | ||
| #if NETCOREAPP | ||
| // don't allocate this inside the loop (CA2014) | ||
| Span<Guid> guidSpan = stackalloc Guid[2]; |
There was a problem hiding this comment.
Keep the partial-trust Guid byte path
When the net461 tracer runs in a partial-trust AppDomain, creating the shared ID generator now JITs a stackalloc/MemoryMarshal.Cast path. The removed fallback explicitly avoided unsafe-style reinterpretation because this code can be reached by manual instrumentation under partial trust; using stack allocation/reinterpretation here can fail before any trace/span IDs are generated. Keep the old Guid.ToByteArray() path for non-NETCOREAPP targets unless partial-trust support is being dropped.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
We explicitly don't support partial-trust, and haven't for a long time, so I don't think this matters? 🤔
There was a problem hiding this comment.
Yeah. It must be referring to the (outdated) comments you removed below:
// we can't use `unsafe` pointers in this code because it can be called
// from manual instrumentation which could be running in partial trust.
// if we ever drop support for partial trust,
// we can rewrite this to use `unsafe` instead of allocating these arrays.
c448c23 to
ed790e1
Compare
25f6d5e to
b1f8279
Compare
| /// </summary> | ||
| /// <returns>The 64-bit FNV hash of the data, as a <c>ulong</c></returns> | ||
| public static ulong GenerateHash(ReadOnlySpan<byte> data, Version version) | ||
| public static ulong GenerateHash(Span<byte> data, Version version) |
There was a problem hiding this comment.
Granted I'm reviewing these PRs out of order, but I'm trying to understand why these went from ReadOnlySpan to Span and I don't think I see why
There was a problem hiding this comment.
No, me neither, I think the AI was stupid and this should be fixed 👀
There was a problem hiding this comment.
Fixed locally, just switched to ReadOnlySpan<T> (I'll push later, to avoid PR rebase stampede 😄 )
ed790e1 to
0688bae
Compare
bouwkast
left a comment
There was a problem hiding this comment.
👍
I presumed that a lot of the deleted/new files are caused by a rebase or the merging of the stacked branches into this one?
I don't remember them from my initial look at this PR specifically but I may have gotten lost along the way of reviewing the stack
b1f8279 to
9b090d5
Compare
Yeah, this was because I stopped rebasing the whole stack every time I merged one of the base PRs, because it was flooding CI 😅 I didn't think about the fact that it would ruin the diffs for reviewing, sorry! 🙁 |
Remove the outer #if NETCOREAPP guard from ValueStringBuilder. Now that Span<T> is in the System namespace for vendored types, the ref struct compiles on .NET Framework too. All dependencies (ArrayPool, MemoryMarshal, MemoryExtensions) are available via vendored global usings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove #if NETCOREAPP around Span<byte>/ReadOnlySpan<byte> overloads of GenerateHash, GenerateV1Hash, and GenerateV1AHash. Delegate the byte[] private methods to the ReadOnlySpan<byte> versions to remove code duplication. The string-accepting methods keep their #if gate as they depend on Encoding.GetBytes(string, Span<byte>). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove #if NETCOREAPP3_1_OR_GREATER around all eight Span<byte> overloads (WriteVarLong, WriteVarLongZigZag, WriteVarInt, WriteVarIntZigZag, ReadVarLong, ReadVarLongZigZag, ReadVarInt, ReadVarIntZigZag). These only index into the span and need no .NET Core-specific BCL methods. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the two-step byte[] allocation path on .NET Framework with a single stackalloc byte[16] + BinaryPrimitives.WriteUInt64LittleEndian on all TFMs. BinaryPrimitives and FnvHash64 Span overloads are now available everywhere via vendored global usings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the byte[]-allocating Guid.ToByteArray() path with stackalloc Guid[2] + MemoryMarshal.Cast<Guid, ulong> on all TFMs. MemoryMarshal is available via vendored global using on .NET Framework. Removes unused _buffer field and GetBuffer helper. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use stackalloc byte[16] for MD5 hash and stackalloc char[36] for hex formatting on all TFMs, avoiding two intermediate array allocations. Now uses the unified Md5Helper.ComputeMd5Hash(string, Span<byte>) signature. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace ArrayPool<char> rent/return for 2-char hex buffer with stackalloc char[2] on all TFMs. The chars are written by HexString.ToHexChars and appended to a StringBuilder, so no ToString/ToArray overhead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The conditional using for System.Buffers is already handled by GlobalUsings.cs which imports the correct namespace (BCL or vendored) based on the TFM. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace StringBuilderCache fallback with ValueStringBuilder and stackalloc on all TFMs. AppendAsLowerInvariant lowercases each segment inline, avoiding the extra string allocation from the previous .ToLowerInvariant() call on the final result. Add a string? overload to ValueStringBuilder.AppendAsLowerInvariant to avoid needing explicit .AsSpan() at each call site. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Now that Span<T>/ReadOnlySpan<T> live in the System namespace on all TFMs,
the RVA-backed `static ReadOnlySpan<byte> Prop => new byte[] {...}` idiom
compiles correctly on net461/netstandard2.0 too. Collapse the NETCOREAPP /
net461 ifdef around CharToHexLookup into a single ReadOnlySpan<byte> property
and drop the now-unused SA1201 pragma block.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete the NETCOREAPP/net461 ifdef in TryExtract and keep only the span-based path. Both HexString.TryParseTraceId and TryParseUInt64 already have ReadOnlySpan<char> overloads, so the shared path works on every TFM — saving two substring allocations per B3 single-header extraction on net461. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d54f3cf to
d1485d3
Compare
#8486) ## Summary of changes Update the tag list generator to always use `ReadOnlySpan<byte>` properties instead of `byte[]` ## Reason for change After moving our vendored `Span<T>` implementation into the `System` namespace, various optimizations open up to us, including using `ReadOnlySpan<byte>` properties on .NET Framework instead of `static readonly byte[]` to avoid startup costs. ## Implementation details Replace the code generated by the generator, and update the generated code ## Test coverage Covered by snapshot tests and behaviour is covered by existing tests. We'll check the benchmarks to make sure that we _don't_ see any perf impact (there shouldn't be, impact should just be reduced startup costs) ## Other details Depends on a stack updating our vendored system code - #8391 - #8454 - #8455 - #8459 - #8461 - #8469 - #8476 - #8477
… string literals (#8487) ## Summary of changes Convert `Encoding.Utf8.GetBytes` calls to static constants using UTF8 string literals ## Reason for change After moving our vendored `Span<T>` implementation into the `System` namespace, various optimizations open up to us, including using UTF-8 string literals, to encode strings to UTF-8 at compile time instead of at runtime. By combing with `static ReadOnlySpan<byte>` properties, these also become zero allocation, so we get reduced overall memory usage as well as better startup time ## Implementation details Replace the following with utf8 string literals: - `StringEncoding.UTF8.GetBytes` - `Encoding.UTF8.GetBytes` - `EncodingHelpers.UTF8NoBom.GetBytes` ## Test coverage All covered by existing tests ## Other details Depends on a stack updating our vendored system code - #8391 - #8454 - #8455 - #8459 - #8461 - #8469 - #8476 - #8477 - #8486
Summary of changes
Remove some branching code that's no longer required after #8476 moved
Span<T>toSystemnamespaceReason for change
This sort of stuff is the reason we made that change, to reduce maintenance.
Implementation details
Set 🤖 looking for possible cases, so it's not exhaustive, but gives a taster. I think most of these make sense. It's nothing outstanding but it's the little things.
Test coverage
Just a refactoring, so covered by existing tests.
Other details
By definition, we don't really expect to see performance improvements for this, other than potentially some reduced allocation in .NET Framework. The primary benefits are devx
Depends on the vendoring code stack:
Depends on a stack updating our vendored system code
ReadOnlySpan<T>,Span<T>et. al. to System namespace #8476